﻿using System.Web.Mvc;
using System.Web.Routing;
using MTO.Framework.Web.Mvc;
using Microsoft.Practices.ServiceLocation;
using Elmah.Contrib.Mvc;
using System.Linq;
using System.Globalization;
using System.Web;
using System;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Configuration;
using System.Data.Common;
using System.Data.Entity.Infrastructure;
using MTO.Library.Data.EF;
using Autofac;
using Autofac.Integration.Mvc;
using System.Reflection;
using StackExchange.Profiling;
using StackExchange.Profiling.MVCHelpers;

namespace MTO.Library.Web
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
    // visit http://go.microsoft.com/?LinkId=9394801

    public class MvcApplication : System.Web.HttpApplication
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            // Ce filtre applique la notion d'authorisation à TOUS les controllers et actions qui existent
            //filters.Add(new MTOAuthorizeAttribute());
         
            filters.Add(new ElmahHandleErrorAttribute());
            filters.Add(new ProfilingActionFilter());
        }

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            routes.IgnoreRoute("{*favicon}", new { favicon = @"(.*/)?favicon.ico(/.*)?" });
            routes.IgnoreRoute("{*allappleicon}", new { allappleicon = @"apple-touch-icon-?.*\.png(/.*)?" });

            routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional });
        }

        protected void Application_Start()
        {
            // Configurer log4net
            log4net.Config.XmlConfigurator.Configure();

            this.InitializeServiceLocator();

            // Initialiser la config du profiler
            this.InitializeProfilerSettings();

            // Initialize le profiler pour qu'il fonctionne avec la base de données
            MiniProfilerEF.Initialize();

            Database.SetInitializer<Entities>(null);

            // Initialiser la configuration de l'application
            ApplicationStartup.Initialize();

            AreaRegistration.RegisterAllAreas();

            ViewEngines.Engines.Clear();
            ViewEngines.Engines.Add(new ProfilingViewEngine(new MTORazorViewEngine()));

            AreaRegistration.RegisterAllAreas();

            RegisterGlobalFilters(GlobalFilters.Filters);
            RegisterRoutes(RouteTable.Routes);
            //I18N.Register();
        }

        protected void Application_BeginRequest()
        {
            MiniProfiler profiler = null;
            
            // might want to decide here (or maybe inside the action) whether you want
            // to profile this request - for example, using an "IsSystemAdmin" flag against
            // the user, or similar; this could also all be done in action filters, but this
            // is simple and practical; just return null for most users. For our test, we'll
            // profile only for local requests (seems reasonable)
            //if (Request.IsLocal)
            //{
                profiler = MiniProfiler.Start();
            //}            
        }

        protected void Application_EndRequest()
        {
            MiniProfiler.Stop();
        }

        protected virtual void InitializeServiceLocator()
        {
            var builder = new ContainerBuilder();

            // Sert à indiquer où sont les controllers
            builder.RegisterControllers(typeof(MTO.Library.Web.Controllers.Home.HomeController).Assembly, typeof(MTO.Framework.Web.Mvc.Controllers.Account.AccountController).Assembly);

            // Enregistrer tous les repositories
            builder.RegisterAssemblyTypes(typeof(MTO.Library.Data.ProductRepository).Assembly).Where(t => t.Name.EndsWith("Repository")).AsImplementedInterfaces();

            // Enregistrer notre DbContext
            builder.RegisterType<Entities>().InstancePerHttpRequest();

            var container = builder.Build();
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
        }

        /// <summary>
        /// Customize aspects of the MiniProfiler.
        /// </summary>
        private void InitializeProfilerSettings()
        {
            var ignored = MiniProfiler.Settings.IgnoredPaths.ToList();

            ignored.Add("/asset.axd"); // exclure les requêtes de Telerik pour communiquer avec son AssetManager

            MiniProfiler.Settings.IgnoredPaths = ignored.ToArray();
            MiniProfiler.Settings.PopupShowTrivial = true;

            // a powerful feature of the MiniProfiler is the ability to share links to results with other developers.
            // by default, however, long-term result caching is done in HttpRuntime.Cache, which is very volatile.
            // 
            // let's rig up serialization of our profiler results to a database, so they survive app restarts.
            //MiniProfiler.Settings.Storage = new Helpers.SqliteMiniProfilerStorage(ConnectionString);

            // different RDBMS have different ways of declaring sql parameters - SQLite can understand inline sql parameters just fine
            // by default, sql parameters won't be displayed
            MiniProfiler.Settings.SqlFormatter = new StackExchange.Profiling.SqlFormatters.InlineFormatter();

            // these settings are optional and all have defaults, any matching setting specified in .RenderIncludes() will
            // override the application-wide defaults specified here, for example if you had both:
            //    MiniProfiler.Settings.PopupRenderPosition = RenderPosition.Right;
            //    and in the page:
            //    @MiniProfiler.RenderIncludes(position: RenderPosition.Left)
            // then the position would be on the left that that page, and on the right (the app default) for anywhere that doesn't
            // specified position in the .RenderIncludes() call.
            //MiniProfiler.Settings.PopupRenderPosition = RenderPosition.Right; //defaults to left
            //MiniProfiler.Settings.PopupMaxTracesToShow = 10;                  //defaults to 15
            //MiniProfiler.Settings.RouteBasePath = "~/profiler";               //e.g. /profiler/mini-profiler-includes.js

            // optional settings to control the stack trace output in the details pane
            // the exclude methods are not thread safe, so be sure to only call these once per appdomain

            //MiniProfiler.Settings.ExcludeType("SessionFactory"); // Ignore any class with the name of SessionFactory
            //MiniProfiler.Settings.ExcludeAssembly("NHibernate"); // Ignore any assembly named NHibernate
            //MiniProfiler.Settings.ExcludeMethod("Flush");        // Ignore any method with the name of Flush
            //MiniProfiler.Settings.StackMaxLength = 256;          // default is 120 characters

            // because profiler results can contain sensitive data (e.g. sql queries with parameter values displayed), we
            // can define a function that will authorize clients to see the json or full page results.
            // we use it on http://stackoverflow.com to check that the request cookies belong to a valid developer.
            //MiniProfiler.Settings.Results_Authorize = (request, profiler) =>
            //{
            //    // we'll use it here to check that a specific page had a query parameter
            //    if ("Home/ResultsAuthorization".Equals(profiler.Name, StringComparison.OrdinalIgnoreCase))
            //    {
            //        // root Timing's Name will always be the profiled request's full url
            //        return profiler.Root.Name.ToLower().Contains("isauthorized");
            //    }
            //    return true; // all other requests are kosher
            //};
        }
    }

    /// <summary>
    /// The entrypoint for internationalization features
    /// </summary>
    public class I18N
    {
        /// <summary>
        /// The default language for all localized keys; when a PO database
        /// is built, the default key file is stored at this locale location
        /// </summary>
        public static string DefaultTwoLetterISOLanguageName { get; set; }

        static I18N()
        {
            DefaultTwoLetterISOLanguageName = "en";
        }

        /// <summary>
        /// Registers the calling web application for automatic language
        /// URL routing based on the existing PO database
        /// </summary>
        public static void Register()
        {
            GlobalFilters.Filters.Add(new LanguageFilter());
            ApplyDecoratorToRoutes();
        }

        private static void ApplyDecoratorToRoutes()
        {
            var routes = RouteTable.Routes;
            using (routes.GetReadLock())
            {
                for (var i = 0; i < routes.Count; i++)
                {
                    routes[i] = new LanguageRouteDecorator(routes[i]);
                }
            }
        }
    }

    /// <summary>
    /// A global filter for automatically redirecting localized requests
    /// to appropriate URLs, as well as setting the resulting language in
    /// the HTTP response
    /// </summary>
    public class LanguageFilter : IActionFilter
    {
        private const string ContentLanguageHeader = "Content-Language";

        public LanguageFilter()
        {
        }

        /// <summary>
        /// Called before an action method executes.
        /// </summary>
        /// <param name="filterContext">The filter context.</param>
        public void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var request = filterContext.HttpContext.Request;
            var values = filterContext.RouteData.Values;

            // Value is already injected from a route declaration
            if (values.ContainsKey("language"))
            {
                return;
            }

            // Value is part of the explicit route and is the preferred language (from those available)
            var preferred = GetLanguageFromSessionOrService(filterContext.HttpContext);
            var url = GetUrlFromRequest(filterContext.HttpContext.Request);
            if (EndsWithAnyIgnoreCase(url,string.Format("/{0}", preferred), string.Format("/{0}/", preferred)))
            {
                return;
            }

            // Value is loose in the query string, i.e. '/?language=en'
            if (request.QueryString["language"] != null)
            {
                Set(filterContext.HttpContext, request.QueryString["language"]);
                preferred = GetLanguageFromSessionOrService(filterContext.HttpContext);
            }

            // Value is part of the explicit route, i.e. '/about/en' but not the preferred language
            var languages = request.UserLanguages ?? new[] { I18N.DefaultTwoLetterISOLanguageName };
            foreach (var language in languages.Where(language => !string.IsNullOrWhiteSpace(language)))
            {
                var semiColonIndex = language.IndexOf(';');
                var token = string.Format("/{0}", semiColonIndex > -1 ? language.Substring(0, semiColonIndex) : language);
                if (!EndsWithAnyIgnoreCase(url,token, string.Format("{0}/", token)))
                {
                    continue;
                }

                // This is an explicit language request, override preferences
                Set(filterContext.HttpContext, token.Substring(1));
                preferred = GetLanguageFromSessionOrService(filterContext.HttpContext);
            }

            RedirectWithLanguage(filterContext, values, preferred);
        }

        public static bool EndsWithAnyIgnoreCase(string input, params string[] args)
        {
            return args.Aggregate(false, (current, arg) => current | input.EndsWith(arg, StringComparison.OrdinalIgnoreCase));
        }

        public virtual string GetUrlFromRequest(HttpRequestBase context)
        {
            var url = context.RawUrl;
            if (url.EndsWith("/") && url.Length > 1)
            {
                // Support trailing slashes
                url = url.Substring(0, url.Length - 1);
            }
            return url;
        }

        public virtual void Set(HttpContextBase context, string language)
        {
            if (context.Session != null)
            {
                context.Session["po:language"] = language;
            }
        }

        private static void RedirectWithLanguage(ControllerContext filterContext, RouteValueDictionary values, string language)
        {
            if (!values.ContainsKey("language"))
            {
                values.Add("language", language);
            }

            var helper = new UrlHelper(filterContext.RequestContext);
            var url = helper.RouteUrl(values);

            var result = new RedirectResult(url);
            result.ExecuteResult(filterContext);
        }

        /// <summary>
        /// Called after the action method executes.
        /// </summary>
        /// <param name="filterContext">The filter context.</param>
        public void OnActionExecuted(ActionExecutedContext filterContext)
        {
            var language = GetLanguageFromSession(filterContext.HttpContext)
                           ?? I18N.DefaultTwoLetterISOLanguageName;

            filterContext.HttpContext.Response.AppendHeader(ContentLanguageHeader, language);
        }

        public static string GetLanguageFromSession(HttpContext context)
        {
            return context.Session != null && context.Session["po:language"] != null
                       ? context.Session["po:language"].ToString()
                       : null;
        }

        public static string GetLanguageFromSession(HttpContextBase context)
        {
            return context.Session != null && context.Session["po:language"] != null
                       ? context.Session["po:language"].ToString()
                       : null;
        }

        public virtual string GetLanguageFromSessionOrService(HttpContextBase context)
        {
            var language = GetLanguageFromSession(context);
            if (language == null)
            {
                var languages = context.Request.UserLanguages;
                language = "en";
                if (context.Session != null)
                {
                    context.Session.Add("po:language", language);
                }
            }
            return language;
        }
    }

    /// <summary>
    /// A route decorator that adapts existing original routes for localization
    /// </summary>
    public class LanguageRouteDecorator : RouteBase
    {
        private RouteBase route;

        public LanguageRouteDecorator(RouteBase route)
        {
            this.route = route;
        }

        public override RouteData GetRouteData(HttpContextBase context)
        {
            var result = route.GetRouteData(context);

            if (result == null)
            {
                var url = GetUrlFromRequest(context.Request);
                var languages = context.Request.UserLanguages ?? new[] { I18N.DefaultTwoLetterISOLanguageName };
                foreach (var language in languages.Where(language => !string.IsNullOrWhiteSpace(language)))
                {
                    var semiColonIndex = language.IndexOf(';');
                    var token = string.Format("/{0}", semiColonIndex > -1 ? language.Substring(0, semiColonIndex) : language);
                    if (!EndsWithAnyIgnoreCase(url,token, token + "/"))
                    {
                        continue;
                    }

                    url = url.TrimEnd(token.Substring(1).ToCharArray());

                    result = route.GetRouteData(context);
                    if (result != null)
                    {
                        // Found the original non-decorated route
                        return result;
                    }
                }
            }

            return result;
        }

        public override VirtualPathData GetVirtualPath(RequestContext context, RouteValueDictionary values)
        {
            var result = route.GetVirtualPath(context, values);

            if (result != null && result.VirtualPath != null)
            {
                var request = context.HttpContext.Request;
                if (!values.ContainsKey("language"))
                {
                    if (request.QueryString["language"] != null)
                    {
                        result.VirtualPath = string.Format("{0}/{1}", result.VirtualPath, request.QueryString["language"]);
                    }
                    else
                    {
                        var language = GetLanguageFromSessionOrService(context.HttpContext);
                        var token = string.Format("/{0}", language);
                        var url = GetUrlFromRequest(context.HttpContext.Request);
                        if (EndsWithAnyIgnoreCase(url,token, string.Format("{0}/", token)))
                        {
                            result.VirtualPath = result.VirtualPath.Equals("")
                                                     ? language
                                                     : string.Format("{0}/{1}", result.VirtualPath, language);
                        }
                    }
                }
                else
                {
                    // Use pre-injected route value
                    var baseUrl = context.HttpContext.Request.Url;
                    if (baseUrl != null)
                    {
                        var relativeUrl = new Uri(baseUrl, values["language"].ToString()).PathAndQuery.Substring(1);
                        result.VirtualPath = relativeUrl;
                    }
                }
            }

            return result;
        }

        public virtual string GetLanguageFromSessionOrService(HttpContextBase context)
        {
            var language = GetLanguageFromSession(context);
            if (language == null)
            {
                var languages = context.Request.UserLanguages;
                language = "en";
                if (context.Session != null)
                {
                    context.Session.Add("po:language", language);
                }
            }
            return language;
        }

        public static string GetLanguageFromSession(HttpContext context)
        {
            return context.Session != null && context.Session["po:language"] != null
                       ? context.Session["po:language"].ToString()
                       : null;
        }

        public static string GetLanguageFromSession(HttpContextBase context)
        {
            return context.Session != null && context.Session["po:language"] != null
                       ? context.Session["po:language"].ToString()
                       : null;
        }

        public virtual string GetUrlFromRequest(HttpRequestBase context)
        {
            var url = context.RawUrl;
            if (url.EndsWith("/") && url.Length > 1)
            {
                // Support trailing slashes
                url = url.Substring(0, url.Length - 1);
            }
            return url;
        }

        public static bool EndsWithAnyIgnoreCase(string input, params string[] args)
        {
            return args.Aggregate(false, (current, arg) => current | input.EndsWith(arg, StringComparison.OrdinalIgnoreCase));
        }
    }
}